package spitfire.ksim.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.text.DecimalFormat;
import java.text.Format;
import java.util.Arrays;
import java.util.List;

import javax.swing.JPanel;

import spitfire.ksim.util.TPoint;

public class Plotter2DPanel extends JPanel{
	
	private static final long serialVersionUID = 2894579392077276843L;
	
	public static final byte NO_AXIS = 0x0;
	public static final byte X_AXIS_ONLY = 0x1;
	public static final byte Y_AXIS_ONLY = 0x2;
	public static final byte X_AND_Y = 0x3;
	
	private double[] X;
	private double[] Y;
	private int plotFromIndex;
	private int plotToIndex;
	private double dataWindowLeft;
	private double dataWindowRight;
	private double dataWindowBottom;
	private double dataWindowTop;
	private int paddingLeft = 0;
	private int paddingRight = 0;
	private int paddingBottom = 0;
	private int paddingTop = 0;
	private int yAxisLoc = 0;
	private int xAxisLoc = 0;
	private byte axisStatus = NO_AXIS;
	private Color pointColor = Color.RED;
	private Color lineColor = Color.RED; 
	private Format xLabelFormat = new DecimalFormat("0.00");
	private Format yLabelFormat = new DecimalFormat("0.00");
	private boolean isConnected = false;
	private Plotter2DRuler ruler = null;
	
	private class Plotter2DRuler {
		private int x;
		private int y;
		private TPoint valuePoint = new TPoint(0,0);
		private Point paintPoint = new Point(0,-100);
		private Color color;
		
		public Plotter2DRuler(Color color) {
			this.color = color;
		}
		
		public void paintRuler(Graphics g) {
//			System.out.println(x + ", " + y);
			calPosition();
//			System.out.println(x + ", " + y);
			g.setColor(color);
			g.drawLine(paintPoint.x, paddingTop, paintPoint.x, getHeight() - paddingBottom);
			g.drawLine(paddingLeft, paintPoint.y, getWidth() - paddingRight, paintPoint.y);
		}

		private void calPosition() {
			if (plotFromIndex == plotToIndex) {
				return;
			}
			valuePoint = getValueAtLoc(x, y);
			if (isConnected) {
				int p1 = Arrays.binarySearch(X, plotFromIndex, plotToIndex, valuePoint.x);
				int p2 = 0;
				if (p1 < 0 ) {
					// not found
					p1 = -p1 - 1;
					if (p1 > plotFromIndex && p1 < plotToIndex) {
						p1--;
						p2 = p1 + 1;
						double x1 = X[p1];
						double x2 = X[p2];
						double y1 = Y[p1];
						double y2 = Y[p2];
						valuePoint.y = (x1*y2-y1*x2)/(x1-x2) + (y2-y1)/(x2-x1)*valuePoint.x;
					} else {
						// out of range
						valuePoint.y = 0;
					}
				} else {
					valuePoint.y = X[p1];
				}
			} else {
				int index = plotFromIndex;
				double xMinDist = Double.POSITIVE_INFINITY;
				for (int i = plotFromIndex; i < plotToIndex; i++) {
					double xDist = Math.abs(X[i] - valuePoint.x);
					if (xDist < xMinDist) {
						xMinDist = xDist;
						index = i;
					}
				}
				valuePoint.x = X[index];
				valuePoint.y = Y[index];
			}
			paintPoint = getLocForValue(valuePoint);
		}
	}
	
	public Plotter2DPanel() {
		super();
		addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseMoved(MouseEvent e) {
				if (ruler != null) {
					ruler.x = e.getX();
					repaint();
				}
			}
			
			@Override
			public void mouseDragged(MouseEvent e) {}
		});
	};
	
	public Plotter2DPanel(double[] X, double[] Y, 
			double windowLeft, double windowRight,
			double windowBottom, double windowTop) {
		super();
		setData(X, Y);
	}
	
	public void setData(double[] X, double[] Y) {
		if (X.length != Y.length) {
			throw new RuntimeException("X and Y are of unequal length.");
		}
		this.X = X;
		this.Y = Y;
		plotFromIndex = 0;
		plotToIndex = X.length;
	}
	
	public void setData(Double[] X, Double[] Y) {
		if (X.length != Y.length) {
			throw new RuntimeException("X and Y are of unequal length.");
		}
		this.X = new double[X.length];
		this.Y = new double[Y.length];
		for (int i = 0; i < X.length; i++) {
			this.X[i] = X[i];
			this.Y[i] = Y[i];
		}
		plotFromIndex = 0;
		plotToIndex = X.length;
	}
	
	public void setData(List<? extends Number> X, List<? extends Number> Y) {
		if (X.size() != Y.size()) {
			throw new RuntimeException("X and Y are of unequal length.");
		}
		this.X = new double[X.size()];
		this.Y = new double[Y.size()];
		for (int i = 0; i < X.size(); i++) {
			this.X[i] = X.get(i).doubleValue();
			this.Y[i] = Y.get(i).doubleValue();
		}
		plotFromIndex = 0;
		plotToIndex = X.size();
	}
	
	public void setPlotInterval(int fromIndex, int toIndex) {
		plotFromIndex = fromIndex;
		plotToIndex = toIndex;
	}
	
	public void setDataWindowLeft(double dataWindowLeft) {
		this.dataWindowLeft = dataWindowLeft;
	}

	public double getDataWindowLeft() {
		return dataWindowLeft;
	}

	public void setDataWindowRight(double dataWindowRight) {
		this.dataWindowRight = dataWindowRight;
	}

	public double getDataWindowRight() {
		return dataWindowRight;
	}

	public void setDataWindowBottom(double dataWindowBottom) {
		this.dataWindowBottom = dataWindowBottom;
	}

	public double getDataWindowBottom() {
		return dataWindowBottom;
	}

	/**
	 * @param dataWindowTop the dataWindowTop to set
	 */
	public void setDataWindowTop(double dataWindowTop) {
		this.dataWindowTop = dataWindowTop;
	}

	/**
	 * @return the dataWindowTop
	 */
	public double getDataWindowTop() {
		return dataWindowTop;
	}

	/**
	 * @param yAxisLoc the yAxisLoc to set
	 */
	public void setyAxisLoc(int yAxisLoc) {
		this.yAxisLoc = yAxisLoc;
	}

	/**
	 * @return the yAxisLoc
	 */
	public int getyAxisLoc() {
		return yAxisLoc;
	}

	/**
	 * @param xAxisLoc the xAxisLoc to set
	 */
	public void setxAxisLoc(int xAxisLoc) {
		this.xAxisLoc = xAxisLoc;
	}

	/**
	 * @return the xAxisLoc
	 */
	public int getxAxisLoc() {
		return xAxisLoc;
	}

	public void setPointColor(Color pointColor) {
		this.pointColor = pointColor;
	}

	public Color getPointColor() {
		return pointColor;
	}

	public void setLineColor(Color lineColor) {
		this.lineColor = lineColor;
	}

	public Color getLineColor() {
		return lineColor;
	}

	public void setXLabelFormat(Format nf) {
		this.xLabelFormat = nf;
	}
	
	public void setYLabelFormat(Format nf) {
		this.yLabelFormat = nf;
	}

	public void setAxisStatus(byte axisStatus) {
		this.axisStatus = axisStatus;
	}

	public byte getAxisStatus() {
		return axisStatus;
	}

	public void setConnected(boolean isConnected) {
		this.isConnected = isConnected;
	}

	public boolean isConnected() {
		return isConnected;
	}

	public void setRuler(boolean isPresent) {
		if (isPresent == true) {
			ruler = new Plotter2DRuler(Color.BLUE);
		} else {
			ruler = null;
		}
	}
	
	public void setRulerPosition(Point p) {
		ruler.x = p.x;
		ruler.y = p.y;
	}
	
	public void setRulerColor(Color color) {
		ruler.color = color;
	}
	
	public TPoint getValueAtLoc(Point loc) {
		return getValueAtLoc(loc.x, loc.y);
	}
	
	public TPoint getValueAtLoc(int x, int y) {
		int screenWindowLeft = paddingLeft;
		int screenWindowRight = getWidth() - paddingRight;
		int screenWindowBottom = getHeight() - paddingBottom;
		int screenWindowTop = paddingTop;
		double dataX = (getDataWindowRight() - getDataWindowLeft())
				* (x - screenWindowLeft) / (screenWindowRight - screenWindowLeft)
				+ getDataWindowLeft();
		double dataY = (getDataWindowTop() - getDataWindowBottom())
				* (screenWindowBottom - y) / (screenWindowBottom - screenWindowTop)
				+ getDataWindowBottom();
		return new TPoint(dataX, dataY);
	}

	public Point getLocForValue(TPoint p) {
		return getLocForValue(p.x, p.y);
	}
	
	public Point getLocForValue(double x, double y) {
		int screenWindowLeft = paddingLeft;
		int screenWindowRight = getWidth() - paddingRight;
		int screenWindowBottom = getHeight() - paddingBottom;
		int screenWindowTop = paddingTop;
		double windowX = (screenWindowRight - screenWindowLeft)
				* (x - getDataWindowLeft())
				/ (getDataWindowRight() - getDataWindowLeft())
				+ screenWindowLeft;
		double windowY = (screenWindowBottom - screenWindowTop)
				* (getDataWindowTop() - y)
				/ (getDataWindowTop() - getDataWindowBottom())
				+ screenWindowTop;
		return new Point((int) windowX, (int) windowY);
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		// paint data points and connections
		g.setColor(pointColor);
		for (int i = plotFromIndex; i < plotToIndex; i++) {
			Point p = getLocForValue(X[i], Y[i]);
			g.drawLine(p.x - 1, p.y, p.x + 1, p.y);
			g.drawLine(p.x, p.y - 1, p.x, p.y + 1);
			if (isConnected && i != 0) {
				g.setColor(lineColor);
				Point prevP = getLocForValue(X[i-1], Y[i-1]);
				g.drawLine(prevP.x, prevP.y, p.x, p.y);
			}
		}
		
		paintAxes(g);
		if (ruler != null) {
			paintRulers(g);
			paintValueLabel(g);
		}
	}

	private void paintValueLabel(Graphics g) {
		String xStr = xLabelFormat.format(ruler.valuePoint.x);
		String yStr = yLabelFormat.format(ruler.valuePoint.y);
		String str = xStr + ", " + yStr;
		int labelPosX = ruler.paintPoint.x + 2;
		int labelPosY = ruler.paintPoint.y - 2;
		if (labelPosX + g.getFontMetrics().stringWidth(str) > getWidth()) {
			labelPosX = ruler.paintPoint.x - g.getFontMetrics().stringWidth(str) - 2;
		}
		if (labelPosY - g.getFontMetrics().getHeight()< 0) {
			labelPosY = ruler.paintPoint.y + g.getFontMetrics().getHeight() + 2;
		}
		g.drawString(str, labelPosX, labelPosY);
	}

	private void paintAxes(Graphics g) {
		g.setColor(Color.BLACK);
		if ((getAxisStatus() & X_AXIS_ONLY) != 0) {
			g.drawLine(0, getxAxisLoc(), getWidth(), getxAxisLoc());
			int portion = 10;
			for (int j = 0; j < portion; j++) {
				double value = (dataWindowRight - dataWindowLeft) / portion * j
						+ dataWindowLeft;
				int locX = getLocForValue(value, 0).x;
				g.drawLine(locX, getxAxisLoc() - 1, locX, getxAxisLoc() + 1);
				if (j % 2 == 1) {
					String s = xLabelFormat.format(value);
					g.drawString(s, locX - g.getFontMetrics().stringWidth(s)
							/ 2, getxAxisLoc() - 2);
				}
			}
		}

		if ((getAxisStatus() & Y_AXIS_ONLY) != 0) {
			g.drawLine(getyAxisLoc(), 0, getyAxisLoc(), getHeight());
			int portion = 10;
			for (int j = 0; j < portion; j++) {
				double value = (dataWindowTop - dataWindowBottom) / portion * j
						+ dataWindowBottom;
				int locY = getLocForValue(0, value).y;
				g.drawLine(getyAxisLoc() - 1, locY, getyAxisLoc() + 1, locY);
				if (j % 2 == 1) {
					g.drawString(yLabelFormat.format(value), getyAxisLoc() + 2, locY
							+ g.getFontMetrics().getHeight() / 2);
				}
			}
		}
	}
	
	private void paintRulers(Graphics g) {
		ruler.paintRuler(g);
	}
}
